#ifndef STRENGTHCALCULATORCPU_H
#define STRENGTHCALCULATORCPU_H

#include <set>
#include <algorithm>
#include <math.h>
#include <time.h>
#include <vector>
#include <boost/tuple/tuple.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>

#include "combinations.h"
#include "pack.h"
#include "hand.h"
#include "card.h"

using namespace std;

extern "C" __declspec(dllimport) Float strengthCalculator(const Uint32& sampleNum, const Uint32 currentPlayerNum, const Card* pack, const Uint32 packSize, const Card* board, const Uint32 boardSize, const Hand& myHand, const Uint16* handsList);

typedef boost::array<Card, 7>						CardArray7;
typedef std::vector<Card>							CardVector;

Uint16				handsList[2][13][13][13][13][13];
Uint16				getOverallHandValue(const std::vector<Card>& cards);

class EvaluateHands
{
private:
	const CardArray7&		cards;
	Uint16&					value;

public:
	EvaluateHands(const CardArray7& c, Uint16& val): cards(c), value(val)
	{

	}

	template<typename U>
	void operator()(U x)
	{
		using namespace boost::mpl;

		CardVector		comb;
		comb.reserve(5);
		comb.push_back(cards[at_c<U, 0>::type::value]);
		comb.push_back(cards[at_c<U, 1>::type::value]);
		comb.push_back(cards[at_c<U, 2>::type::value]);
		comb.push_back(cards[at_c<U, 3>::type::value]);
		comb.push_back(cards[at_c<U, 4>::type::value]);
		value			=std::max(value, getOverallHandValue(comb));
	}
};



class Game
{
public:
	typedef enum {CPU, GPU}			TargetDevice;

private:
	typedef enum {AFTER_DEAL, AFTER_FLOP, AFTER_TURN, AFTER_RIVER}			State;

private:
	Byte			currentPlayerNum;
	Hand			myHand;

	Pack			pack;

	CardVector		board;
	State			state;
	Float			pot;
	Float			bet;

public:
	Game(const Byte plys, const Hand& my): currentPlayerNum(plys), myHand(my), state(AFTER_DEAL), pot(0.0f), bet(1.0f)
	{
		pack.erase(remove(pack.begin(), pack.end(), my.first));
		pack.erase(remove(pack.begin(), pack.end(), my.second));
	}

	void setCurrentPlayerNum(const Byte& plys)
	{
		currentPlayerNum		=plys;
	}

	void setMyHand(const Hand& my)
	{
		myHand		=my;
		pack.erase(remove(pack.begin(), pack.end(), my.first));
		pack.erase(remove(pack.begin(), pack.end(), my.second));
	}

	void addToBoard(const Card& card)
	{
		board.push_back(card);
		pack.erase(remove(pack.begin(), pack.end(), card));
	}
	
	void setPot(const Float& p)
	{
		pot			=p;
	}

	void setBet(const Float& b)
	{
		bet			=b;
	}

	Float calculateStrength(const Uint32& sampleNum) const
	{
		Float				value		=0.0f;

		//srand(time(NULL));

		for (Uint32 s=0; s<sampleNum; ++s)
		{
			Pack					pack(this->pack);
			CardVector				board(this->board);

			for (Uint32 i=board.size(); i<5; ++i)
			{
				board.push_back(pack.random());
			}

			Bool				winnerHand		=true;
			Uint32				sameHandValue	=1;

			const CardArray7		mySet		={{myHand.first, myHand.second, board[0], board[1], board[2], board[3], board[4]}};
			Uint16					myValue		=0;

			typedef Combinations<7, 5>::Result			IndexCombinations;
			boost::mpl::for_each<IndexCombinations>(EvaluateHands(mySet, myValue));

			for (Uint32 i=1; i<currentPlayerNum; ++i)
			{
				const Hand&				opponentsHand		=Hand::random(pack);
				const CardArray7		opponentsSet		={{opponentsHand.first, opponentsHand.second, board[0], board[1], board[2], board[3], board[4]}};
				Uint16					opponentsValue		=0;
				
				boost::mpl::for_each<IndexCombinations>(EvaluateHands(opponentsSet, opponentsValue));

				if (myValue==opponentsValue)
				{
					++sameHandValue;
				}

				winnerHand					&=myValue>=opponentsValue;
			}

			if (winnerHand)
			{
				value		+=(1.0f/(Float)sameHandValue);
			}
		}

		return value/(Float)sampleNum;
	}

	String getWhatTodo(const TargetDevice& device)
	{
		srand(time(NULL));
		typedef enum {CHECK, FOLD, CALL, RAISE}			Todo;

		static const Char*			todoString[]		={"Check", "Fold", "Call", "Raise"};
		const Uint32		sampleNum			=2000;

		//const Float&		handStrength		=device==GPU ? strengthCalculator(1000, currentPlayerNum, (const Card*)(&pack[0]), pack.size(), board.empty() ? NULL : (const Card*)(&board[0]), board.size(), myHand, &handsList[0][0][0][0][0][0]) : calculateStrength(1000);
		const Float&		handStrength		=calculateStrength(sampleNum);
		const Float&		handStrengthGPU		=strengthCalculator(sampleNum, currentPlayerNum, (const Card*)(&pack[0]), pack.size(), board.empty() ? NULL : (const Card*)(&board[0]), board.size(), myHand, &handsList[0][0][0][0][0][0]);
		const Float&		invPotOdds			=(bet+pot)/bet;
		const Float&		ratio				=handStrength*invPotOdds;
		const Float&		randBeh				=(Float)(rand()%100)/100.0f;

		Todo				todo;

		if (ratio<0.75f)
		{
			if (randBeh<0.97)
			{
				todo		=FOLD;
			} else
			{
				todo		=RAISE;
			}
		} else if (ratio<1.0f)
		{
			if (randBeh<0.75f)
			{
				todo		=FOLD;
			} else if (randBeh<0.85)
			{
				todo		=CALL;
			} else
			{
				todo		=RAISE;
			}
		} else if (ratio<1.25f)
		{
			if (randBeh<0.85f)
			{
				todo		=CALL;
			} else
			{
				todo		=RAISE;
			}
		} else
		{
			if (randBeh<0.2f)
			{
				todo		=CALL;
			} else
			{
				todo		=RAISE;
			}
		}

		if (ratio<1.0f)
		{
			todo		=FOLD;
		} else if (ratio<1.3f)
		{
			todo		=CALL;
		} else
		{
			todo		=RAISE;
		}

		return todoString[todo];
	}
};

void loadHandsList()
{
	memset(handsList, 0, sizeof(Uint16)*2*13*13*13*13*13);
	InputFileStream			file("handlist.txt");

	for (Uint32 i=0; i<7462; ++i)
	{
		Uint16			s;
		Uint16			c1, c2, c3, c4, c5;
		file >> s >> c1 >> c2 >> c3 >> c4 >> c5;
		
		handsList[s][c1][c2][c3][c4][c5]		=7561-i;
	}

	file.close();
}

Uint16 getOverallHandValue(const CardVector& cards)
{
	typedef boost::array<Byte, 5>						RankArray5;
	
	RankArray5		ranks;

	for (Uint32 i=0; i<ranks.size(); ++i)
	{
		ranks[i]		=cards[i].getRank();
	}
	sort(ranks.rbegin(), ranks.rend());

	const Byte		suitedInd		=cards[0].getSuit()==cards[1].getSuit()==cards[2].getSuit()==cards[3].getSuit()==cards[4].getSuit() ? 0 : 1;

	if (ranks[0]==12)
	{
		return std::max(handsList[suitedInd][ranks[0]][ranks[1]][ranks[2]][ranks[3]][ranks[4]], handsList[suitedInd][ranks[1]][ranks[2]][ranks[3]][ranks[4]][ranks[0]]);
	} else
	{
		return handsList[suitedInd][ranks[0]][ranks[1]][ranks[2]][ranks[3]][ranks[4]];
	}
}
#endif